/*
 * Copyright (c) 2010-2011, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "alkes/opengl/GLRenderTarget.h"
#include "alkes/opengl/GLImageBuffer.h"
#include "alkes/opengl/GLGraphics.h"

#if (ALKES_API_WIN32 == 1)
// temporary!!
#pragma warning(disable:4100)
#endif

namespace alkes {

class OpenGLWriterDelegate
    : public WriterDelegate
{
public:
    OpenGLWriterDelegate(const Rect& target_rect, const Rect& rect)
        : target_rect_(target_rect)
        , rect_(rect)
    {
    }

    virtual void write(ImageWriter& writer, PixelFormat format, int32_t pitch, int32_t width, int32_t height)
    {

#if !defined(ALKES_OGL_DISABLE_ReadBuffer)
        glReadBuffer(GL_FRONT);
#endif

        Rect::value_type y = target_rect_.height - (rect_.y + 1);
        Rect::value_type y_limit = y - rect_.height;
        GLenum gl_format = GL_RGBA;
        GLenum gl_type = GL_UNSIGNED_BYTE;
        uint8_t* pixels = (uint8_t*)alkes_malloc(pitch);

        if (pixels)
        {
            for (; y > y_limit; --y)
            {
                glReadPixels(rect_.x, y, width, 1, gl_format, gl_type, pixels);
                invokeWriteScanline(writer, format, pixels, pitch, pitch, target_rect_.height - (y + 1));
            }

            alkes_free(pixels);
        }
    }

private:
    Rect target_rect_;
    Rect rect_;
};

GLRenderTarget* GLRenderTarget::create(const Rect& size, IGraphics3D* owner, TargetBehavior target_behavior)
{
    return new GLRenderTarget(size, owner, target_behavior);
}

void GLRenderTarget::destroy()
{
    delete this;
}

GLRenderTarget::GLRenderTarget(const Rect& size, IGraphics3D* owner, TargetBehavior target_behavior)
: IRenderTarget3D(owner, target_behavior)
, target_rect_(size)
{
}

GLRenderTarget::~GLRenderTarget()
{
}

GLGraphics* GLRenderTarget::getGLGraphics() const
{
    return static_cast<GLGraphics*>(getIGraphics3D());
}

void GLRenderTarget::enableDepthBuffer(bool is_enable)
{
    target_state_.setFlag(DEPTH_BUFFER_ENABLE, is_enable);
}

bool GLRenderTarget::isEnableDepthBuffer() const
{
    return target_state_[DEPTH_BUFFER_ENABLE];
}

void GLRenderTarget::onSnapshot(ImageWriter& writer, const Rect& rect)
{
    Rect target_rect(getTargetRect());
    if (!target_rect.contains(rect))
        return ;

    OpenGLWriterDelegate deleg(target_rect, rect);

    writer.writeDelegate(AL_PIXEL_FORMAT_RGBA8888, rect.width * 4, rect.width, rect.height, &deleg);
}

void GLRenderTarget::beginDraw2D()
{
    getGLGraphics()->beginDraw2D(this);
}

void GLRenderTarget::endDraw2D()
{
    getGLGraphics()->endDraw2D();
}

const Rect GLRenderTarget::getTargetRect() const
{
    return target_rect_;
}

void GLRenderTarget::setScissorRect(const Rect& rect)
{
    getGLGraphics()->setScissorRect(rect);
}

const Rect GLRenderTarget::getScissorRect()
{
    return getGLGraphics()->getScissorRect();
}

void GLRenderTarget::setState(int state, bool enable)
{
    getGLGraphics()->setState(state, enable);
}

bool GLRenderTarget::getState(int state)
{
    return getGLGraphics()->getState(state);
}

void GLRenderTarget::setAlphaBlendMode(AlphaBlendMode src_blend, AlphaBlendMode dst_blend)
{
    getGLGraphics()->setAlphaBlendMode(src_blend, dst_blend);
}

void GLRenderTarget::getAlphaBlendMode(AlphaBlendMode* src_blend, AlphaBlendMode* dst_blend)
{
    getGLGraphics()->getAlphaBlendMode(src_blend, dst_blend);
}

void GLRenderTarget::pushMatrix()
{
    getGLGraphics()->pushMatrix();
}

void GLRenderTarget::popMatrix()
{
    getGLGraphics()->popMatrix();
}

void GLRenderTarget::translate(float x, float y)
{
    getGLGraphics()->translate(x, y);
}

void GLRenderTarget::scale(float xscale, float yscale, const Vector2D& origin)
{
    getGLGraphics()->scale(xscale, yscale, origin);
}

void GLRenderTarget::rotate(float angle, const Vector2D& origin)
{
    getGLGraphics()->rotate(angle, origin);
}

void GLRenderTarget::skewX(float angle)
{
    getGLGraphics()->skewX(angle);
}

void GLRenderTarget::skewY(float angle)
{
    getGLGraphics()->skewY(angle);
}

void GLRenderTarget::beginDraw3D()
{
    getGLGraphics()->beginDraw3D(this);
}

void GLRenderTarget::endDraw3D()
{
    getGLGraphics()->endDraw3D();
}

void GLRenderTarget::setViewport(const Viewport& viewport)
{
    getGLGraphics()->setViewport(viewport);
}

const Viewport GLRenderTarget::getViewport() const
{
    return getGLGraphics()->getViewport();
}

void GLRenderTarget::setPerspective(float angle, float aspect, float znear, float zfar)
{
    getGLGraphics()->setPerspective(angle, aspect, znear, zfar);
}

void GLRenderTarget::setLookAt(const Vector3D& eye, const Vector3D& at, const Vector3D& up)
{
    getGLGraphics()->setLookAt(eye, at, up);
}

void GLRenderTarget::translate(float x, float y, float z)
{
    getGLGraphics()->translate(x, y, z);
}

void GLRenderTarget::scale(float xscale, float yscale, float zscale)
{
    getGLGraphics()->scale(xscale, yscale, zscale);
}

void GLRenderTarget::rotate(float angle, float x, float y, float z)
{
    getGLGraphics()->rotate(angle, x, y, z);
}

void GLRenderTarget::clearBuffer(uint32_t buffer_flag, const Color& clear_color, const Rect* rect, float depth)
{
    getGLGraphics()->clearBuffer(buffer_flag, clear_color, rect, depth);
}

bool GLRenderTarget::drawPrimitive(IVertexBuffer* vbuffer, PrimitiveType type, uint32_t start, uint32_t count)
{
    return getGLGraphics()->drawPrimitive(vbuffer, type, start, count);
}

bool GLRenderTarget::setTexture(Image* image, int stage)
{
    return getGLGraphics()->setTexture(image, stage);
}

void GLRenderTarget::setTextureFilter(TexFilterValue min_filter, TexFilterValue mag_filter, int stage)
{
    getGLGraphics()->setTextureFilter(min_filter, mag_filter, stage);
}

void GLRenderTarget::getTextureFilter(TexFilterValue* min_filter, TexFilterValue* mag_filter, int stage)
{
    getGLGraphics()->getTextureFilter(min_filter, mag_filter, stage);
}

void GLRenderTarget::setTextureAddressMode(TexAddressMode u_mode, TexAddressMode v_mode, int stage)
{
    getGLGraphics()->setTextureAddressMode(u_mode, v_mode, stage);
}

void GLRenderTarget::getTextureAddressMode(TexAddressMode* u_mode, TexAddressMode* v_mode, int stage)
{
    getGLGraphics()->getTextureAddressMode(u_mode, v_mode, stage);
}

}
